package ar.uba.fi.RSACryptoFiuba.view.encrypt;

import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.LayoutStyle.ComponentPlacement;

import org.apache.commons.lang3.math.NumberUtils;

import ar.uba.fi.RSACryptoFiuba.model.InvalidValueException;
import ar.uba.fi.RSACryptoFiuba.model.RSAAlgorithmEvaluationStrategy;
import ar.uba.fi.RSACryptoFiuba.model.RSAAlgorithmLearningStrategy;
import ar.uba.fi.RSACryptoFiuba.model.decrypt.RSADecryptMessage;
import ar.uba.fi.RSACryptoFiuba.model.encrypt.RSAEncryptAlgorithm;
import ar.uba.fi.RSACryptoFiuba.model.encrypt.RSAEncryptMessage;
import ar.uba.fi.RSACryptoFiuba.model.keygeneration.RSAKeyGenerationMessage;
import ar.uba.fi.RSACryptoFiuba.view.commons.ButtonsPanel;
import ar.uba.fi.RSACryptoFiuba.view.commons.EvaluationInteractionPanel;
import ar.uba.fi.RSACryptoFiuba.view.commons.ResultsPanel;


@SuppressWarnings("serial")
public class EncryptEvaluationPanel extends JPanel implements Observer {
	
	private EncryptProgressPanel progressPanel;
	
    private JPanel cards;
    private CardLayout cardsLayout;
    private EncryptConfigurationPanel encryptConfigurationPanel;
    private EvaluationInteractionPanel generateNumericalRepresentationPanel;
    private List<EvaluationInteractionPanel> encryptPanels;
    private ResultsPanel encryptResultsPanel;
    private JSeparator lineSeparator;
    private ButtonsPanel buttonsPanel;
            
    private RSAEncryptAlgorithm algorithm;
    private RSAEncryptMessage message;
    private boolean isAlgorithmConfigurated;
    
	private RSAAlgorithmEvaluationStrategy evaluationStrategy;
	private RSAAlgorithmLearningStrategy learningStrategy;
    	
	public EncryptEvaluationPanel(ActionListener modeButtonActionListener, RSAKeyGenerationMessage keyGenerationMessage, RSAEncryptMessage encryptMessage, RSADecryptMessage decryptMessage){
		this.message = encryptMessage;
		this.evaluationStrategy = new RSAAlgorithmEvaluationStrategy();
		this.learningStrategy = new RSAAlgorithmLearningStrategy();
		initComponentes(modeButtonActionListener, keyGenerationMessage, decryptMessage);
	}
	
	private void initComponentes(ActionListener modeButtonActionListener, RSAKeyGenerationMessage keyGenerationMessage, RSADecryptMessage decryptMessage){		
		
		//Progress Panel
        progressPanel = new EncryptProgressPanel();
        
        //Steps Panel
        cards = new JPanel();
        cards.setPreferredSize(new Dimension(670, 165));
        
        cardsLayout = new CardLayout();
        cards.setLayout(cardsLayout);
        
        encryptConfigurationPanel = new EncryptConfigurationPanel(keyGenerationMessage, decryptMessage);
        cards.add(encryptConfigurationPanel, "ENCRYPT_CONFIGURATION");
        
        generateNumericalRepresentationPanel = new EvaluationInteractionPanel("M:");
        cards.add(generateNumericalRepresentationPanel, "GENERATE_NUMERICAL_REPRESENTATION");
	        
		//Buttons Panel
        lineSeparator = new JSeparator();        
	    
		ActionListener firstButtonActionListener = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				resetCards();
				encryptConfigurationPanel.resetErrors();
				if(!algorithm.getCurrentStep().isFirst()){
	    			algorithm.reset();
        		}
				cardsLayout.first(cards);
				isAlgorithmConfigurated = false;
				handleButtonsEnable();
			}
		};  

		ActionListener previousButtonActionListener = new ActionListener() {
	    	public void actionPerformed(ActionEvent e) {
	    		if(!algorithm.getCurrentStep().isFirst()){   			
	    			algorithm.movePreviousStep();
	    			algorithm.undoStep();
	    		}else{
	    			isAlgorithmConfigurated = false;
	    			encryptConfigurationPanel.resetErrors();
	    		}
        		cardsLayout.previous(cards);
        		resetCards();	    		
	    		handleButtonsEnable();
	    	}
	    };

	    ActionListener nextButtonActionListener = new ActionListener() {
        	public void actionPerformed(ActionEvent e) {
        		if(!isAlgorithmConfigurated){
        			configurateAlgorithm();
        			if(isAlgorithmConfigurated){          		
        				cardsLayout.next(cards);
        				handleButtonsEnable();
        				encryptConfigurationPanel.resetErrors();
        				algorithm.getMessage().update();
        			}
        		}else{       		
	        		JPanel card = getCurrentCard();
	        		if(card instanceof EvaluationInteractionPanel){
	        			((EvaluationInteractionPanel) card).resetToolTip();
	        			algorithm.getMessage().setElementToEvaluate(((EvaluationInteractionPanel) card).getOperationValue());
	        		}	        	       
					try {
						algorithm.executeStep();
						algorithm.moveNextStep();
						cardsLayout.next(cards);
						resetCards();
					} catch (InvalidValueException ex) {
						if (card instanceof EvaluationInteractionPanel) {
							((EvaluationInteractionPanel) card).setToolTip(ex.getMessage());
						}

					}
					handleButtonsEnable();
    			}
    		}	
        };

        ActionListener lastButtonActionListener = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if(!isAlgorithmConfigurated){
        			configurateAlgorithm();
        			if(isAlgorithmConfigurated){
        				resolve();
        			}
        		} else{
        			resolve();
        		}
			}
			
			private void resolve(){
				if(!algorithm.getCurrentStep().isLast()){
					algorithm.setStrategy(learningStrategy);
					algorithm.resolve();
					cardsLayout.last(cards);
					algorithm.undoStep();
        			algorithm.setStrategy(evaluationStrategy);
        		}
				handleButtonsEnable();
			}
		};
	  
		buttonsPanel = new ButtonsPanel(modeButtonActionListener, firstButtonActionListener, previousButtonActionListener, nextButtonActionListener, lastButtonActionListener);
		
		//--------------------------------

		GroupLayout encryptTabLayout = new GroupLayout(this);
        this.setLayout(encryptTabLayout);
        encryptTabLayout.setHorizontalGroup(
            encryptTabLayout.createParallelGroup(Alignment.LEADING)
            .addGroup(encryptTabLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(encryptTabLayout.createParallelGroup(Alignment.LEADING)
                    .addComponent(lineSeparator, Alignment.TRAILING)
                    .addComponent(progressPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(cards, GroupLayout.DEFAULT_SIZE, 830, Short.MAX_VALUE)
                    .addComponent(buttonsPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        encryptTabLayout.setVerticalGroup(
            encryptTabLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
            .addGroup(encryptTabLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(progressPanel, GroupLayout.PREFERRED_SIZE, 81, GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(ComponentPlacement.RELATED)
                .addComponent(cards, GroupLayout.DEFAULT_SIZE, 159, Short.MAX_VALUE)
                .addPreferredGap(ComponentPlacement.RELATED)
                .addComponent(lineSeparator, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(ComponentPlacement.UNRELATED)
                .addComponent(buttonsPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addGap(6, 6, 6))
        );
	}

	public void update(Observable o, Object arg) {
		RSAEncryptMessage message = (RSAEncryptMessage) o;
		this.progressPanel.setMessageValue(message.getPlainText());
		this.progressPanel.setPublicKeyValue(String.format("(%s, %s)", message.getEKey(), message.getNKey()));
				
		String numericalRepresentationValue = message.getNumericalRepresentation();
		if(!message.getNumericalRepresentation().equals(message.getRemainNumericalRepresentation())){
			String processed = message.getProcessedNumericalRepresentation();
			String remain = message.getRemainNumericalRepresentation();		
			numericalRepresentationValue = "[ " + processed  + " ] " + remain;
		}
		this.progressPanel.setNumericalRepresentationValue(numericalRepresentationValue);
		
		this.progressPanel.setCryptogramValue(message.getEncryptedText());
		
		encryptConfigurationPanel.setEValue(message.getEKey());
		encryptConfigurationPanel.setNValue(message.getNKey());
		encryptConfigurationPanel.setMessageValue(message.getPlainText());
		
		generateNumericalRepresentationPanel.setOperationValue(message.getNumericalRepresentation());
		
		for (int i = 0; i < encryptPanels.size(); i++) {
			encryptPanels.get(i).setOperationValue(message.getActualC());
		}
		
		encryptResultsPanel.setResultsValues("Mensaje", "Criptograma", message.getPlainText(), message.getEncryptedText());
        
        handleButtonsEnable();
		
	}
		
	public void handleButtonsEnable(){
		this.buttonsPanel.getFirstButton().setEnabled(isAlgorithmConfigurated);
		this.buttonsPanel.getPreviousButton().setEnabled(isAlgorithmConfigurated);
		this.buttonsPanel.getNextButton().setEnabled(!algorithm.getCurrentStep().isLast());
		this.buttonsPanel.getLastButton().setEnabled(!algorithm.getCurrentStep().isLast());
	}
	
	public void configurateAlgorithm() {
		String e = encryptConfigurationPanel.getEValue();
		String n = encryptConfigurationPanel.getNValue();
		String plainText = encryptConfigurationPanel.getMessageValue();
		if(areValidParameters(e, n, plainText)){
			message.setEKey(e);
			message.setNKey(n);
			message.setPlainText(plainText);
			this.algorithm = new RSAEncryptAlgorithm(evaluationStrategy, message);
			this.algorithm.getMessage().addObserver(this);
			isAlgorithmConfigurated = true;
			encryptPanels = new ArrayList<EvaluationInteractionPanel>();
			for (int i = 0; i < algorithm.getEncryptionSteps(); i++) {
				EvaluationInteractionPanel encryptPanel = new EvaluationInteractionPanel("c:");
				encryptPanels.add(encryptPanel);
			}
			
			List<Component> componentsToRemove = new ArrayList<Component>();
	        for (int i = 2; i < cards.getComponents().length; i++) {
				componentsToRemove.add(cards.getComponent(i));
			}
	        for (Component component : componentsToRemove) {
				cards.remove(component);
			}
	        
			for (int i = 0; i < encryptPanels.size(); i++) {
				cards.add(encryptPanels.get(i), "ENCRYPT_"+i);
			}
			
			encryptResultsPanel = new ResultsPanel();
	        cards.add(encryptResultsPanel, "ENCRYPT_RESULTS");
	        this.isAlgorithmConfigurated = true;
		}else{
			encryptConfigurationPanel.setErrors();
		}
	}
	
	private boolean areValidParameters(String e, String n, String plainText) {
		return NumberUtils.isDigits(e) && NumberUtils.isDigits(n) && !"".equals(plainText);
	}
	
	private JPanel getCurrentCard(){ 
		JPanel currentCard = null;
		for (Component component : cards.getComponents()) {
			if (component.isVisible()) {
				currentCard = (JPanel) component;
				break;
			}
		}
		return currentCard;
	}	

	private void resetCards() {
		JPanel card = getCurrentCard();
		if(card instanceof EvaluationInteractionPanel){
			((EvaluationInteractionPanel) card).setOperationValue("");
			((EvaluationInteractionPanel) card).resetToolTip();
		}
	}
	
}	

